package com.digitaldreamers.framework.network;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import com.digitaldreamers.framework.BaseConstants;
import com.digitaldreamers.framework.network.packet.AbSendablePacket;

class ConnectionHandler implements IConnectionHandler, Runnable {
    private static ConnectionHandler instance;
	private static final Object lockObj = new Object();
    
    private String mIP;
    private int mPort;
    private volatile boolean mIsRunning;
    private boolean mIsConnected;
    private Socket mSocket;
    private DataOutputStream mDataOutputStream;
    private InputStream mInputStream;
    private Thread mDataReceivingThread;
    private ArrayList<IOnDataReceivedListener> mListeners;
	
	public static ConnectionHandler getInstance() {
		if(instance == null) {
			synchronized(lockObj) {
				if(instance == null) {
					instance = new ConnectionHandler();
				}
			}
		}
		return instance;
	}
    
    public boolean isRunning() {
        return mIsRunning;
    }
    
    public boolean isConnected() {
        return mIsConnected;
    }
    
    private ConnectionHandler() {
    	mIP = BaseConstants.DEFAULT_GAME_SERVER_IP;
    	mPort = BaseConstants.DEFAULT_GAME_SERVER_PORT;
    	mIsRunning = false;
    	mIsConnected = false;
    	mListeners = new ArrayList<IOnDataReceivedListener>();
    }
    
    @Override
	public void addOnDataReceivedListener(IOnDataReceivedListener pListener) {
    	mListeners.add(pListener);
	}
	
	@Override
	public void removeOnDataReceivedListener(IOnDataReceivedListener pListener) {
		mListeners.remove(pListener);
	}
    
    @Override
    public void connectToServer(String pIP, int pPort) throws UnknownHostException, IOException {
        if (mIsConnected) {
            return;
        }

        if(pIP != null && !"".equals(pIP)) {
        	mIP = pIP;
        }
        
        if(pPort > 0) {
        	mPort = pPort;
        }
        
        mSocket = new Socket(mIP, mPort);
        mInputStream = mSocket.getInputStream();
        mDataOutputStream = new DataOutputStream(new BufferedOutputStream(mSocket.getOutputStream()));
        mDataReceivingThread = new Thread(this);
        mDataReceivingThread.start();
        mIsRunning = true;
        mIsConnected = true;
    }
    
    @Override
    public void send(AbSendablePacket pSendablePacket) throws IOException {
        byte[] data = pSendablePacket.toByteArray();
        
        // Send the length of the data first
        mDataOutputStream.write(convertIntToByteArray(data.length));
        mDataOutputStream.flush();
        
        // Then send the real data
        mDataOutputStream.write(data);
        mDataOutputStream.flush();
    }
    
    @Override
    public void close() {
    	if(mIsRunning) {
	    	mIsRunning = false;
	        try {
	        	mDataReceivingThread.join();
	        	mInputStream.close();
	        	mDataOutputStream.close();
	        	mSocket.close();
	        } catch (Exception e) {
	        	e.printStackTrace();
	        }
	        
	        mIsConnected = false;
	        mDataReceivingThread = null;
            mInputStream = null;
            mDataOutputStream = null;
            mSocket = null;
    	}
    }

    @Override
	public void run() {
    	while (mIsRunning) {
			try {
				if (mInputStream.available() > 0) {
					byte[] data = receiveFixedLengthData(mInputStream, BaseConstants.PACKET_LENGTH_BYTES);
					if (data == null) {
						throw new Exception(BaseConstants.ERROR_MESSAGE__DATA_READING);
					}

					// Get message length
					ByteArrayInputStream BAIS = new ByteArrayInputStream(data);
					DataInputStream DIS = new DataInputStream(BAIS);
					int packetLength = DIS.readInt();
					DIS.close();
					
					if (packetLength > 0) {
						data = receiveFixedLengthData(mInputStream, packetLength);
						if (data == null) {
							throw new Exception(BaseConstants.ERROR_MESSAGE__DATA_READING);
						}
					}
					
					//checkNotification(data);
					fireDataReceivedEvent(data);
				}
				
				Thread.sleep(BaseConstants.SLEEP_TIME);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

    private byte[] receiveFixedLengthData(InputStream pInputStream, int pCount) throws Exception {
        // Check the data.
        if (pCount <= 0 || pCount > BaseConstants.MAX_PACKET_LENGTH_BYTES) {
            throw new IllegalArgumentException(BaseConstants.ERROR_MESSAGE__INVALID_PACKET_LENGTH);
        }

        // Set the buffer with the input length.
        byte[] buffer = new byte[pCount];
        int receivedCount;
        int position = 0;
        
        // Read until all data is received.
        do {
            // Read the stream to buffer.
            receivedCount = pInputStream.read(buffer, position, pCount - position);
            if (receivedCount == 0) {
                throw new Exception(BaseConstants.ERROR_MESSAGE__DATA_READING);
            }
            position += receivedCount;
        } while (position < pCount);
        
        return buffer;
    }
    
    private void fireDataReceivedEvent(byte[] pData) {
		IOnDataReceivedListener[] listeners = new IOnDataReceivedListener[mListeners.size()];
		mListeners.toArray(listeners);
		for (IOnDataReceivedListener listener : listeners) {
			listener.onDataReceived(pData);
		}
	}
    
    private byte[] convertIntToByteArray(int pValue) {
        return new byte[] {
            (byte) (pValue >>> 24),
            (byte) (pValue >>> 16),
            (byte) (pValue >>> 8),
            (byte) pValue
        };
    }
    
	/*
	 * // check notification private void checkNotification(byte[] data) { try {
	 * ByteArrayInputStream BAIS = new ByteArrayInputStream(data);
	 * DataInputStream DIS = new DataInputStream(BAIS); int packetID =
	 * DIS.readInt(); if (packetID == 99) { // Read the packet int iStatusID =
	 * DIS.readInt(); String strMessage = DIS.readUTF(); String strSenderName =
	 * DIS.readUTF();
	 * 
	 * // } } catch (IOException e) { // TODO Auto-generated catch block
	 * e.printStackTrace(); }
	 * 
	 * }
	 */
}
